package com.geline.mybatisplus.util;

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.date.LocalDateTimeUtil;
import cn.hutool.core.util.ReflectUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.annotation.OrderBy;
import com.baomidou.mybatisplus.annotation.SqlCondition;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.TableInfo;
import com.baomidou.mybatisplus.core.metadata.TableInfoHelper;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.core.toolkit.sql.SqlInjectionUtils;
import com.baomidou.mybatisplus.core.toolkit.sql.StringEscape;
import com.geline.cloud.util.DynamicSqlUtil;
import com.geline.mybatisplus.annotation.QueryModel;
import com.geline.mybatisplus.domain.PageQuery;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.format.annotation.DateTimeFormat;

import java.lang.reflect.Field;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.regex.Pattern;

/**
 * QueryWrapper查询条件构造工具类
 *  解析：@QueryModel, @TableId, @TableField, @OrderBy
 *  支持：@TableField(value = "d.dept_name like '%${handleDeptName}%' or d2.dept_name like '%${handleDeptName}%'", condition = "")
 *
 * @author: mengx
 * @date: 2021/5/9 13:17
 */
public class QueryWrapperUtil {
    private static final Logger log = LoggerFactory.getLogger(QueryWrapperUtil.class);

    /**
     * 储存select columns信息
     */
    private static final Map<Class<?>, String[]> SqlSelect_CACHE = new ConcurrentHashMap<>();

    /**
     * 构造查询条件对象
     * @param queryModel
     * @param <T>
     * @return
     */
    public static <T> QueryWrapper build(T queryModel){
        return build(queryModel, null, null);
    }

    public static <T> QueryWrapper build(T queryModel, PageQuery pageQuery){
        return build(queryModel, pageQuery.getOrderByColumn(), pageQuery.getIsAsc());
    }

    /**
     * 构造查询条件对象
     * @param queryModel
     * @param orderByColumn  排序字段
     * @param isAsc 排序方式 asc, desc
     * @param <T>
     * @return
     */
    public static <T> QueryWrapper build(T queryModel, String orderByColumn, String isAsc){
        Class clazz = queryModel.getClass();
        QueryWrapper queryWrapper = Wrappers.query();

        QueryModel annotation = (QueryModel) clazz.getAnnotation(QueryModel.class);
        if(annotation != null){
            String table = annotation.table();
            Objects.requireNonNull(table, String.format("%s @QueryModel table不能为空", queryModel.getClass().getSimpleName()));
            TableInfo tableInfo = TableInfoHelper.getTableInfo(table);
            queryWrapper.setEntityClass(tableInfo.getEntityType());
            String sqlSelect = annotation.sqlSelect();
            if(StrUtil.isNotBlank(sqlSelect)){
                queryWrapper.select(sqlSelect);
            }
        }

        //用于保存排序字段信息
        TreeMap<Short, OrderBy> orderByMap = new TreeMap<>();
        Map<Short, String> fieldValueMap = new HashMap<>();
        Field[] fields = ReflectUtil.getFields(clazz);
        for(Field field : fields){
            Object fieldValue = ReflectUtil.getFieldValue(queryModel, field.getName());
            TableId tableId = field.getAnnotation(TableId.class);
            if(!StrUtil.isBlankIfStr(fieldValue) && tableId != null){
                //处理 @TableId(value = "id")
                String condition = SqlCondition.EQUAL;
                String value = String.valueOf(fieldValue);
                if(isCharSequence(field.getType())){
                    condition = condition.replace("#{%s}", "'"+value+"'");
                }else {
                    condition = condition.replace("#{%s}", value);
                }
                condition = condition.replace("%s", tableId.value());
                queryWrapper.apply(condition);
                continue;
            }
            TableField tableField = field.getAnnotation(TableField.class);
            OrderBy orderBy = field.getAnnotation(OrderBy.class);
            if(orderBy != null && tableField != null){
                orderByMap.put(orderBy.sort(), orderBy);
                fieldValueMap.put(orderBy.sort(), tableField.value());
            }

            if(tableField!=null && !StrUtil.isBlankIfStr(fieldValue)){
                String condition = tableField.condition();
                if(fieldValue.getClass().isArray()){
                    List valList = new ArrayList();
                    Object[] values = (Object[])fieldValue;
                    for(Object value : values){
                        valList.add(value);
                    }
                    //处理List.class
                    if(valList.size()>0){
                        String sql = getConditionSql(valList, tableField);
                        queryWrapper.apply(sql);
                    }
                }else if(fieldValue instanceof Collection){
                    //处理List.class
                    List valList = new ArrayList();
                    valList.addAll((Collection) fieldValue);
                    if(valList.size()>0){
                        String sql = getConditionSql(valList, tableField);
                        queryWrapper.apply(sql);
                    }
                } else {
                    //处理非List.class，替换#{%s}
                    String valueStr = getWrapValue(field, fieldValue);

                    if(StrUtil.isBlank(condition)){
                        String value = tableField.value();
                        if(value.contains("${") || value.contains("#{")){
                            Map<String, Object> example = new HashMap<>();
                            example.put(field.getName(), valueStr);
                            String sql = DynamicSqlUtil.parseSQL(value, example);
                            queryWrapper.apply(sql);
                        }
                    }else {
                        if(condition.toLowerCase().contains(" like ")){
                            //like 语句转义 '%_\
                            valueStr = escapeStr(valueStr);
                        }else if(!condition.toLowerCase().contains(" in")){
                            //其它查询条件转义'\
                            valueStr = valueStr.replaceAll("['\\\\]", "\\\\$0");
                            //不用检验SQL注入
                            //validParaBySafeSql(value);
                        }
                        // %s LIKE CONCAT('%%',#{%s},'%%')
                        condition = StrUtil.isNotBlank(condition) ? condition : SqlCondition.EQUAL;
                        if(isNumber(fieldValue.getClass())){
                            condition = condition.replace("#{%s}", valueStr);
                        }else {
                            condition = condition.replace("#{%s}", StringEscape.escapeString(valueStr));
                        }
                        //替换 ${%s}
                        condition = condition.replace("${%s}", valueStr);
                        //替换 %s LIKE CONCAT('%%','xxx','%%')
                        condition = condition.replace("%s", tableField.value());
                        queryWrapper.apply(condition);
                    }
                }
            }
        }
        if(StrUtil.isNotBlank(orderByColumn)){
            queryWrapper.orderBy(true, "asc".equals(isAsc), StrUtil.toUnderlineCase(orderByColumn));
        }else {
            Set<Short> keySet = orderByMap.keySet();
            keySet.stream().forEach(key -> {
                // order by user_name desc
                queryWrapper.orderBy(true, orderByMap.get(key).asc(), fieldValueMap.get(key));
            });
        }
        return queryWrapper;
    }

    private static String getConditionSql(List valList, TableField tableField){
        Class<?> aClass = valList.get(0).getClass();
        String condition = tableField.condition();
        if(isCharSequence(aClass)){
            if(condition.toLowerCase().indexOf("between")!=-1){
                // date between '2020-01-01' and '2020-08-01'
                condition = condition.replaceFirst("#\\{%s\\}", StrUtil.wrap((String) valList.get(0), "'"));
                condition = condition.replaceFirst("#\\{%s\\}", StrUtil.wrap((String) valList.get(1), "'"));
            }else {
                String valueStr = StrUtil.join("','", valList.toArray());
                condition = condition.replace("#{%s}", StrUtil.wrap(valueStr, "'"));
            }
        }else {
            String valueStr = StrUtil.join(",", valList.toArray());
            condition = condition.replace("#{%s}", valueStr);
        }
        //替换 %s
        condition = condition.replace("%s", tableField.value());
        return condition;
    }

    public static String getWrapValue(Field field, Object fieldValue){
        if(fieldValue instanceof Date){
            DateTimeFormat dtf = field.getAnnotation(DateTimeFormat.class);
            String format = dtf==null ? "yyyy-MM-dd" : dtf.pattern();
            String valueStr = DateUtil.format((Date)fieldValue, format);
            return valueStr;
        } else if(fieldValue instanceof LocalDate){
            DateTimeFormat dtf = field.getAnnotation(DateTimeFormat.class);
            String format = dtf==null ? "yyyy-MM-dd" : dtf.pattern();
            String valueStr = LocalDateTimeUtil.format((LocalDate)fieldValue, format);
            return valueStr;
        } else if(fieldValue instanceof LocalDateTime){
            DateTimeFormat dtf = field.getAnnotation(DateTimeFormat.class);
            String format = dtf==null ? "yyyy-MM-dd hh:mm:ss" : dtf.pattern();
            String valueStr = LocalDateTimeUtil.format((LocalDateTime)fieldValue, format);
            return valueStr;
        }else {
            return String.valueOf(fieldValue);
        }
    }

    //获取查询SQL
    public static <T> String buildSql(T queryModel){
        return buildSql(queryModel, null, null);
    }

    //获取查询SQL
    public static <T> String buildSql(T queryModel, String orderByColumn, String isAsc){
        QueryModel annotation = queryModel.getClass().getAnnotation(QueryModel.class);
        if(annotation == null){
            throw new RuntimeException("can not find @QueryModel by "+queryModel.getClass());
        }
        String from = annotation.from();
        QueryWrapper query = build(queryModel, orderByColumn, isAsc);
        if(StrUtil.isNotBlank(from)){
            return String.format("select %s from %s %s", query.getSqlSelect(), from, query.getCustomSqlSegment());
        }else {
            String table = annotation.table();
            return String.format("select %s from %s %s", query.getSqlSelect(), table, query.getCustomSqlSegment());
        }
    }

    //获取查询SQL
    public static <T> String buildSql(T queryModel, QueryWrapper query){
        QueryModel annotation = queryModel.getClass().getAnnotation(QueryModel.class);
        String from = annotation.from();
        if(StrUtil.isNotBlank(from)){
            return String.format("select %s from %s %s", annotation.sqlSelect(), from, query.getCustomSqlSegment());
        }else {
            String table = annotation.table();
            return String.format("select %s from %s %s", annotation.sqlSelect(), table, query.getCustomSqlSegment());
        }
    }

    //获取select count(1) SQL
    public static <T> String buildCountSql(T queryModel, String orderByColumn, String isAsc){
        QueryModel annotation = queryModel.getClass().getAnnotation(QueryModel.class);
        String from = annotation.from();
        QueryWrapper query = build(queryModel, orderByColumn, isAsc);
        if(StrUtil.isNotBlank(from)){
            return String.format("select count(1) from %s %s", from, query.getCustomSqlSegment());
        }else {
            String table = annotation.table();
            return String.format("select count(1) from %s %s", table, query.getCustomSqlSegment());
        }
    }

    /**
     * 获取查询字段数组
     * @param clazz
     * @param <T>
     * @return
     */
    public static <T> String[] getSqlSelect(Class clazz){
        String[] sqlSelectArr = SqlSelect_CACHE.get(clazz);
        if(sqlSelectArr != null){
            return sqlSelectArr;
        }
        List<String> columnsList = new ArrayList<>();
        //用于保存排序字段信息
        Field[] fields = ReflectUtil.getFields(clazz);
        for(Field field : fields){
            TableId tableId = field.getAnnotation(TableId.class);
            if(tableId != null){
                String column = tableId.value();
                String fieldName = field.getName();
                String camelName = StrUtil.toCamelCase(column);
                if(fieldName.equals(column) || fieldName.equals(camelName)){
                    columnsList.add(column);
                }else {
                    columnsList.add(column+" as "+fieldName);
                }
            }

            TableField tableField = field.getAnnotation(TableField.class);
            if(tableField != null && tableField.select()){
                String column = tableField.value();
                String fieldName = field.getName();
                String camelName = StrUtil.toCamelCase(column);
                if(fieldName.equals(column) || fieldName.equals(camelName)){
                    columnsList.add(column);
                }else {
                    columnsList.add(column+" as "+fieldName);
                }
            }
        }
        if(columnsList.size() > 0){
            //设置自定义查询字段信息
            sqlSelectArr = columnsList.toArray(new String[]{});
            SqlSelect_CACHE.put(clazz, sqlSelectArr);
            return sqlSelectArr;
        }
        return null;
    }

    public static boolean isCharSequence(Class<?> clazz) {
        return clazz != null && CharSequence.class.isAssignableFrom(clazz);
    }

    public static boolean isNumber(Class<?> clazz) {
        return clazz != null && Number.class.isAssignableFrom(clazz);
    }

    /**
     * 判断是否为合法字符(a-zA-Z0-9-_)
     *
     * @param text
     * @return
     */
    public static boolean isRightfulString(String text) {
        return match(text, "^[A-Za-z0-9_-]+$");
    }

    /**
     * 正则表达式匹配
     *
     * @param text 待匹配的文本
     * @param reg  正则表达式
     * @return
     */
    private static boolean match(String text, String reg) {
        if (StringUtils.isBlank(text) || StringUtils.isBlank(reg)) {
            return false;
        }
        return Pattern.compile(reg).matcher(text).matches();
    }

    //https://blog.csdn.net/weixin_36074841/article/details/114558032
    private static void validParaBySafeSql(String text) {
        boolean check = SqlInjectionUtils.check(text);
        if(check){
            log.warn("检测到SQL注入攻击 = {}", text);
            throw new RuntimeException("检测到SQL注入攻击:"+text);
        }
    }

    //转义 '%_\
    public static String escapeStr(String content){
        if (StrUtil.isEmpty(content)) {
            return StrUtil.str(content);
        }
        return content.replaceAll("['%_\\\\]", "\\\\$0");
    }
}
